package uk.ac.aber.paws.client.net;

import java.io.*;
import java.net.*;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Level;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import uk.ac.aber.paws.client.power.*;
import uk.ac.aber.paws.client.state.LoginProcessOperations;
import uk.ac.aber.paws.client.constants.Constants;
import uk.ac.aber.paws.client.core.Initiator;
import uk.ac.aber.paws.client.core.Operations;
import uk.ac.aber.paws.client.core.PAWSClient;
import uk.ac.aber.paws.client.db.*;
import static uk.ac.aber.paws.client.constants.NetworkConstants.*;

/**
 * @author Rob Johnson, Aberystwyth University
 * @email rob.johnson@aber.ac.uk
 * @date 16th August 2011
 * 
 * SecureConnectionHandler.java
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

public class SecureConnectionHandler implements Runnable {

	private SSLSocket socket;
	
	public SecureConnectionHandler(SSLSocket socket) { 
		   
		this.socket = socket; 
		
		// start a new thread to handle this request
		Thread t = new Thread(this); 
		t.start(); 
	    
	}
	
	public void run() { 
		
		try { 
			
			
			ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); 
		            
			// read in the request sent to this ServerSocket
			String message = (String) ois.readObject(); 
		   
			// sleep request sent by PAWS Server
			if (message.equals(SLEEP_COMMAND) && PAWSClient.SYSTEM_STATE.getPowerEventInfo().getLastWake() == -1) {
		
				ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); 
				
				Initiator.LOG_FILE.log(Level.INFO,"Sleep request received from server.");
				
				// check that the socket connection is being received from the dedicated PAWS Server
				if (validateRemote() || validateLocal()) {
					
					oos.close();
					
					// action a sleep event on this computer
					boolean success = SleepOperations.initiateServerInitiatedSleep();
					
					// return the status of the sleep request back to PAWS Server
					//if (success) {
						//oos.writeObject(SLEEP_PROCESS_INITIATED);
					//}
					//else {
						//oos.writeObject(SLEEP_PROCESS_DENIED);
					//}
				}
				
				oos.close();
			}
			
			// LogMeOut is requesting an automatic logout to be sanctioned by the PAWS Client
			else if (message.equals(AUTO_LOGOFF_MESSAGE)) {
				
				// only accept a connection if received from itself i.e. the loopback address
				if (validateLocal()) {
					(new DBLogger()).registerForcedAutoLogoutEvent(PAWSClient.SYSTEM_STATE.getComputerInfo().getComputerName(),(String)ois.readObject());
				}
			}
			
			// A pen drive has been detected in this computer, this request asks PAWS Client to log the event to 
			// the database
			else if (message.equals(PENDRIVE_MESSAGE)) {
				
				if (validateLocal()) {
					(new DBLogger()).registerPendriveEvent(PAWSClient.SYSTEM_STATE.getComputerInfo().getComputerName(),(String)ois.readObject());
				}
			}
			
			// the IdleLogger has sent the current idle status of the users on this computer
			else if (message.equals(REPORT_IDLE_TIME)) {
				
				// only if sent by IdleLogger running on the same computer
				if (validateLocal()) {
					
					Long idleTime = (Long)ois.readObject();
					String userName = (String)ois.readObject();
				
					String os = PAWSClient.SYSTEM_STATE.getComputerInfo().getOperatingSystem();
					
					// for the benefit of MACs and Linux, check that user log in object present
				//	if (os.equals(Constants.OS_FLAG_LINUX) ||
					//		os.equals(Constants.OS_FLAG_MAC)) {
						
					//	LoginProcessOperations.checkForLogin(userName);	
					//}
					
					PAWSClient.SYSTEM_STATE.getLoginRegister().registerIdleTime(userName.toUpperCase(),idleTime.longValue());
					
					// log the idle time for the old logging system - windows
					//PAWSClient.SYSTEM_STATE.getLoginObj().setIdleTime(idleTime.longValue(),userName);
							
					Initiator.LOG_FILE.log(Level.ALL,"Idle Time recorded for "+userName+" as: "+idleTime);
				}
				
			}
			
			// the PAWS Server has requested the current idle time of users on this computer
			// in order to determine whether the computer should be slept
			else if (message.equals(REQUEST_CLIENT_IDLE_TIME)) {
				
				ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
				
				// only if the message has been sent from the specified PAWS Server
				if (validateRemote()) {
				
					//Hashtable<String,Long> idleTimes = PAWSClient.SYSTEM_STATE.getLoginObj().getIdleTimes();
					
					Hashtable<String,Long> idleTimes = PAWSClient.SYSTEM_STATE.getLoginRegister().getIdleTimes();
					
					oos.writeObject(idleTimes);
					
					Initiator.LOG_FILE.log(Level.ALL,"Idle Time requested by server, provided as: "+idleTimes.toString());
				}
				else {
					
				}
				oos.close();
			}
			
			// the PAWS Server has requested the current idle time of users on this computer
			// in order to determine whether the computer should be slept
			else if (message.equals(REQUEST_GET_LOGININFO)) {
				
				ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
				
				// only if the message has been sent from itself
				if (validateLocal()) {
				
					Vector<Hashtable<String,Object>> loginInfo = PAWSClient.SYSTEM_STATE.getLoginRegister().getLoginInfo();
					
					oos.writeObject(loginInfo);
					
				}
				else {
					
				}
				oos.close();
			}
			
			// this request is sent by the IdleLogger in order to gather session information to
			// display in the SysTray facility
			else if (message.equals(REQUEST_CLIENT_INFORMATION)) {
				
				ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); 
				
				// only if requested by IdleLogger running on this same computer
				if (validateLocal()) {
					
					Hashtable dataHash = PAWSClient.SYSTEM_STATE.exportData();
					
					oos.writeObject(dataHash);
					
					Initiator.LOG_FILE.log(Level.ALL,"Idle Time requested by server, provided as: "+PAWSClient.SYSTEM_STATE.getUserIdleTime());
				}
				
				oos.close();
			}
			
			// sent by the IdleLogger to request PAWS Client to sanction a reboot of this computer
			else if (message.equals(REQUEST_RESTART_SERVICE)) {
				
				// only if requested by IdleLogger running on this same computer
				if (validateLocal()) {
					Operations.cleanCloseDown(true);
				}
			}
			else if (message.equals(SEND_STOP_MESSAGE)) {
				
				// only if requested by IdleLogger running on this same computer
				if (validateLocal()) {
					Operations.cleanCloseDown(false);
				}
			}
			// reject any other unrecognised socket connection
			else {
				
				Initiator.LOG_FILE.log(Level.ALL,"Rejected unknown message from: "+socket.getInetAddress().toString());
				
				ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); 
				
				oos.writeObject(SLEEP_PROCESS_DENIED);
				
				oos.close();
			}
			ois.close(); 
			
			
			    
		}
		catch (SSLException e) {
			e.printStackTrace();
		}
		catch (IOException e) { 
			e.printStackTrace();         
		} 
		catch (ClassNotFoundException e) { 
			e.printStackTrace();         
		}
		
		finally {
			
			try {
				this.socket.close();
			} 
			catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	} 
	
	/*
	 * This procedure checks if the server socket connection has come from the local host
	 * loopback adapter. This ensures that only requests sent from the same client computer are
	 * responded to.
	 * @return Returns true if the connecting socket is 127.0.0.1, false otherwise
	 */
	private boolean validateLocal() {
		
		Initiator.LOG_FILE.log(Level.ALL,this.socket.getInetAddress().getHostAddress()+"....is calling");
		
		if (this.socket.getInetAddress().getHostAddress().equals(PAWSClient.getConfig().getProperty("app.main.net.loopbackaddress"))) {
			return true;
		}
		
		return false;
	}
	
	/* 
	 * This procedure, using the app.main.net.servercommsaddress config value, validates that the
	 * connecting server socket is actually the registered PAWS Server. This will try and prevent
	 * non authorised hosts sending requests to the client.
	 * @return Returns true if the calling server is a trusted host, false, if the IP/host is not recognised.
	 */
	private boolean validateRemote() {
		
		Initiator.LOG_FILE.log(Level.ALL,this.socket.getInetAddress().getHostAddress()+"....is calling");
		String trustedServerHost = PAWSClient.getConfig().getProperty("app.main.net.servercommsaddress");
		Initiator.LOG_FILE.log(Level.ALL,"Trusted server host name = "+trustedServerHost);
		
		DNSLookup dns = new DNSLookup();
		
		InetAddress[] trustedAddresses;
		
		try {
			trustedAddresses = dns.lookupAllHostAddr(trustedServerHost);
			
			for (int i=0;i<trustedAddresses.length;i++) {
				
				Initiator.LOG_FILE.log(Level.ALL,"Trusted server host name = "+"Trusted IPaddress = "+trustedAddresses[i].getHostAddress());
				
				if (trustedAddresses[i].getHostAddress().equals(this.socket.getInetAddress().getHostAddress())) {
					
					Initiator.LOG_FILE.log(Level.ALL,"Accepted server connection from "+this.socket.getInetAddress().getHostAddress());
					
					return true;
				}
				
			}
		} 
		catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		Initiator.LOG_FILE.log(Level.ALL,"Rejected server connection from "+this.socket.getInetAddress().getHostAddress());
		
		return false;
	}
}
